#include "Framework/StableHeaders.h"
#include "Geometry/CustomObject.h"
#include "Geometry/Object.h"
#include "Math/Vector3.h"
#include "Geometry/CustomObjectSection.h"
#include "Graphics/VertexData.h"
#include "Graphics/VertexDeclaration.h"

#include "Graphics/VertexElement.h"
#include "Graphics/VertexIndexData.h"
#include "Graphics/IDeviceVertexBuffer.h"
#include "Graphics/VertexBufferBinding.h"
#include "Scene/IRenderQueue.h"

namespace tyro
{
	namespace Geometry
	{
		

		#define TEMP_INITIAL_SIZE 50
		#define TEMP_VERTEXSIZE_GUESS sizeof(float) * 12
		#define TEMP_INITIAL_VERTEX_SIZE TEMP_VERTEXSIZE_GUESS * TEMP_INITIAL_SIZE
		#define TEMP_INITIAL_INDEX_SIZE sizeof(UINT_32) * TEMP_INITIAL_SIZE

		CustomObject::CustomObject( const String& name, Graphics::IDeviceBufferManager* mgr)
			:	Object(name)
			,	bufferManager(mgr)
			,	activeSection(0)
			,	firstVertex(true)
			,	vertexPending(false)
			,	tempVertexBuffer(0)
			,	tempVertexSize(TEMP_INITIAL_VERTEX_SIZE)
			,	tempIndexBuffer(0)
			,	tempIndexSize(TEMP_INITIAL_INDEX_SIZE)
			,	declSize(0)
			,	estVertexCount(0)
			,	estIndexCount(0)
			,	texCoordIndex(0)
		{
		}


		void CustomObject::AddVertex( FLOAT_32 x, FLOAT_32 y, FLOAT_32 z )
		{
			if (!activeSection)
			{
				return;
			}

			if (vertexPending)
			{
				CopyVertexToBuffer();
				firstVertex = false;
			}

			if (firstVertex && !currentUpdating)
			{
				activeSection->GetRenderOperation()->vertexData->vertexDeclaration->AddElement(0, declSize, VET_FLOAT3, VES_POSITION, 0);
				declSize += Graphics::VertexElement::GetTypeSize(VET_FLOAT3);
			}
			

			tempVertex.vertex.x = x;
			tempVertex.vertex.y = y;
			tempVertex.vertex.z = z;

			texCoordIndex = 0;

			vertexPending = true;

		}

		void CustomObject::AddVertex( const Math::Vector3f& vec )
		{
			AddVertex(vec.x, vec.y, vec.z); 
		}

		void CustomObject::AddNormal( const Math::Vector3f& vec )
		{
			AddNormal(vec.x, vec.y, vec.z);
		}

		void CustomObject::AddNormal( FLOAT_32 x, FLOAT_32 y, FLOAT_32 z )
		{
			if (!activeSection)
			{
				return;
			}

			if (firstVertex && !currentUpdating)
			{
				activeSection->GetRenderOperation()->vertexData->vertexDeclaration->AddElement(0, declSize, VET_FLOAT3, VES_NORMAL);
				declSize += Graphics::VertexElement::GetTypeSize(VET_FLOAT3);
			}

			tempVertex.normal.x = x;
			tempVertex.normal.y = y;
			tempVertex.normal.z = z;


		}

		void CustomObject::BeginBuild( const String& materialName, Graphics::MaterialManager* matMgr, RenderType renderType /*= RT_TRIANGLE_LIST*/, const String& groupName /*= IO::ResourceGroupManager::DEFAULT_GROUP_NAME*/ )
		{
			if (activeSection)
			{
				return;
			}


			activeSection = new CustomObjectSection(this, materialName, matMgr, renderType, groupName);
			activeSection->GetRenderOperation()->vertexData->InitVertexData(bufferManager);
			currentUpdating = false;
			sectionList.push_back(activeSection);
			firstVertex = true;
		}

		void CustomObject::AddTextureCoord( FLOAT_32 u, FLOAT_32 v )
		{
			if (!activeSection)
			{
				return;
			}

			if (firstVertex && !currentUpdating)
			{
				activeSection->GetRenderOperation()->vertexData->vertexDeclaration
					->AddElement(0, declSize, VET_FLOAT2, VES_TEXTURE_COORDINATES, texCoordIndex);
				declSize += Graphics::VertexElement::GetTypeSize(VET_FLOAT2);
			}

			tempVertex.textCoordDims[texCoordIndex] = 2;
			tempVertex.texCoord[texCoordIndex].x = u;
			tempVertex.texCoord[texCoordIndex].y = v;

			++texCoordIndex;
		}

		void CustomObject::AddTriangle( UINT_32 x, UINT_32 y, UINT_32 z )
		{
			if (!activeSection)
			{
				return;
			}

			if(activeSection->GetRenderOperation()->renderType != RT_TRIANGLE_LIST)
			{

			}

			Index(x);
			Index(y);
			Index(z);
			
		}

		void CustomObject::Index( UINT_32 index )
		{
			if (!activeSection)
			{
				return;
			}

			indexed = true;
			if (index >= 65536)
			{
				activeSection->Set32BitIndex(true);
			}

			// Create index buffer if needed
			Graphics::IRenderOperation* renOp = activeSection->GetRenderOperation();
			if (!renOp->indexData)
			{
				renOp->indexData = new Graphics::VertexIndexData;
				renOp->indexData->indexCount = 0;
			}
			renOp->useIndicies = true;


			// Copy index to temp buffer
			ResizeIndexBuffer(++renOp->indexData->indexCount);
			tempIndexBuffer[renOp->indexData->indexCount - 1] = index;

		}

		CustomObjectSection* CustomObject::GetSection( UINT_32 index ) const
		{
			if (index >= sectionList.size())
			{
				return NULL;
			}
			return sectionList[index];
		}

		void CustomObject::EndBuild()
		{
			if (!activeSection)
			{
				return;
			}

			// Copy any waiting vertex data to buffer
			if (vertexPending)
			{
				CopyVertexToBuffer();
			}

			CustomObjectSection* result = NULL;

			Graphics::IRenderOperation* renOp =  activeSection->GetRenderOperation();

			if (renOp->vertexData->vertexCount == 0 || 
				(renOp->useIndicies && renOp->indexData->indexCount == 0))
			{

				if (currentUpdating)
				{
					result = activeSection;
				} 
				else
				{
					sectionList.pop_back();
					delete activeSection;
				}
			}
			else
			{
				Graphics::IDeviceVertexBufferSharedPtr vertexBuff;
				bool createVertexBuffer = true;
				bool createIndexBuffer = renOp->useIndicies;
				VertexIndexType indexType = activeSection->Is32BitIndices() ? VIT_32BIT : VIT_16BIT;

				if (currentUpdating)
				{
					// Determine if the vertex buffer needs creating
					vertexBuff = renOp->vertexData->vertexBufferBinding->GetBuffer(0);
					if(vertexBuff->GetNumberVertices() >= renOp->vertexData->vertexCount)
					{
						createVertexBuffer = false;
					}

					// Determine if the index buffer needs creating
					if (renOp->useIndicies)
					{
						if ((renOp->indexData->indexBuffer->GetNumberIndecies() >= renOp->indexData->indexCount)
							&& (indexType == renOp->indexData->indexBuffer->GetType()))
						{
							createIndexBuffer = false;
						}
					}
						
				}

				// Create vertex buffer if needed
				if (createVertexBuffer)
				{
					size_t vertexCount = max(renOp->vertexData->vertexCount, estVertexCount);
					vertexBuff = bufferManager->CreateVertexBuffer(declSize, vertexCount);
					renOp->vertexData->vertexBufferBinding->SetBinding(0, vertexBuff);
				}

				// Create index buffer if needed
				if (createIndexBuffer)
				{
					size_t indexCount = max(renOp->indexData->indexCount, estVertexCount);
					renOp->indexData->indexBuffer = bufferManager->CreateIndexBuffer(indexType, indexCount);
				}

				// Write vertex data to device
				vertexBuff->WriteData( 0, renOp->vertexData->vertexCount * vertexBuff->GetVertexSize(), tempVertexBuffer);


				// Write index data to device
				if (renOp->useIndicies)
				{
					if (indexType == VIT_32BIT)
					{
						// Write 32-bit data straight to device
						renOp->indexData->indexBuffer->WriteData(0, renOp->indexData->indexCount *
							renOp->indexData->indexBuffer->GetIndexSize(), tempIndexBuffer);
					}
					else
					{
						// Write 16-bit data to device
						// Retrieve buffer base pointer
						USHORT_16* deviceIndexPtr = static_cast<USHORT_16*>(renOp->indexData->indexBuffer->Lock());
						UINT_32* sourceIndexPtr = tempIndexBuffer;
						// Write temp index buffer to device
						for (size_t i = 0; i < renOp->indexData->indexCount; i++)
						{
							*deviceIndexPtr++ = static_cast<USHORT_16>(*sourceIndexPtr++);
						}
						renOp->indexData->indexBuffer->Unlock();
					}
				}
				
				result = activeSection;



			}


			activeSection = 0;



		}

		void CustomObject::ResizeIndexBuffer( size_t indexCount )
		{
			size_t newSize = indexCount * sizeof(UINT_32);
			if (newSize > tempIndexSize || !tempIndexBuffer)
			{
				if (!tempIndexBuffer)
				{
					newSize = tempIndexSize;
				}
				else
				{
					newSize = max(newSize, tempIndexSize*2);
				}

				indexCount = newSize / sizeof(UINT_32);
				UINT_32* tmp = tempIndexBuffer;

				tempIndexBuffer = new UINT_32[indexCount];
				if (tmp)
				{
					memcpy(tempIndexBuffer, tmp, tempIndexSize);
					delete tmp;
				}

				tempIndexSize = newSize;


			}
		}

		void CustomObject::CopyVertexToBuffer( void )
		{
			Graphics::IRenderOperation* renOp = activeSection->GetRenderOperation();
			if(renOp->vertexData->vertexCount == 0 && !currentUpdating)
			{
				Graphics::VertexDeclaration* oldDcl = renOp->vertexData->vertexDeclaration;
				//TODO Vertex declaration sorting
			}
			ResizeVertexBuffer(++renOp->vertexData->vertexCount);

			char* basePtr = tempVertexBuffer + (declSize * (renOp->vertexData->vertexCount-1));

			const Graphics::VertexDeclaration::VertexElementList& elemList = 
				renOp->vertexData->vertexDeclaration->GetElements();

			for (Graphics::VertexDeclaration::VertexElementList::const_iterator i = elemList.begin();
				i != elemList.end(); ++i)
			{
				FLOAT_32*	floatPtr = 0;
				RGBA*		rgbaPtr = 0;
				const Graphics::VertexElement&	elem = *i;

				switch(elem.GetType())
				{
				case VET_FLOAT1:
				case VET_FLOAT2:
				case VET_FLOAT3:
				case VET_FLOAT4:
					elem.SetBasePointerToElement(basePtr, &floatPtr);
					break;
				case VET_COLOUR:
				case VET_COLOUR_ABGR:
				case VET_COLOUR_ARGB:
					elem.SetBasePointerToElement(basePtr, &rgbaPtr);
					break;
				default:
					break;
				};

				USHORT_16 dims;
				switch(elem.GetSemantic())
				{
				case VES_POSITION:
					*floatPtr++ = tempVertex.vertex.x;
					*floatPtr++ = tempVertex.vertex.y;
					*floatPtr++ = tempVertex.vertex.z;
					break;
				case VES_NORMAL:
					*floatPtr++ = tempVertex.normal.x;
					*floatPtr++ = tempVertex.normal.y;
					*floatPtr++ = tempVertex.normal.z;
					break;
				case VES_TANGENT:
					*floatPtr++ = tempVertex.tangent.x;
					*floatPtr++ = tempVertex.tangent.y;
					*floatPtr++ = tempVertex.tangent.z;
					break;
				case VES_TEXTURE_COORDINATES:
					dims = Graphics::VertexElement::GetTypeCount(elem.GetType());
					for (USHORT_16 t = 0; t < dims; ++t)
						*floatPtr++ = tempVertex.texCoord[elem.GetIndex()][t];
					break;
				case VES_DIFFUSE:
					//rs = Root::getSingleton().getRenderSystem();
					//if (rs)
					//	rs->convertColourValue(mTempVertex.colour, pRGBA++);
					//else
					//	*rgbaPtr++ = mTempVertex.colour.getAsRGBA(); // pick one!
					break;
				default:
					// nop ?
					break;
				};


			}
		}

		void CustomObject::ResizeVertexBuffer( size_t vertexCount )
		{
			size_t newSize;
			if (!firstVertex)
			{
				newSize = declSize * vertexCount;
			}
			else
			{
				newSize = TEMP_VERTEXSIZE_GUESS  * vertexCount;
			}

			if (newSize > tempVertexSize || !tempVertexBuffer)
			{
				if (!tempVertexBuffer)
				{
					newSize = tempVertexSize;
				} 
				else
				{
					newSize = max(newSize, tempVertexSize*2);
				}

				char* tmp = tempVertexBuffer;
				tempVertexBuffer = new char[newSize];

				if (tmp)
				{
					memcpy(tempVertexBuffer, tmp, tempVertexSize);
					delete tmp;
				}

				tempVertexSize = newSize;

			}
		}

		void CustomObject::UpdateRenderQueue( Scene::IRenderQueue* renderQueue )
		{
			for(CustomObjectSectionList::iterator i = sectionList.begin(); i != sectionList.end(); ++i)
			{

				Graphics::IRenderOperation* rend = (*i)->GetRenderOperation();

				// If section has no data continue
				if (rend->vertexData->vertexCount == 0 ||
					(rend->useIndicies && rend->indexData->indexCount == 0))
				{
					continue;
				}

				// Add renderable to que
				renderQueue->AddRenderable((*i), renderQueue->GetDefaultGroup());
			}
		}

	}
}